/*
 *  Copyright (c) 2023-2025 苏州棱镜七彩信息科技有限公司 (Suzhou PrismAurora Information Technology Co.,Ltd)
 *  sbom-generate is licensed under Mulan PSL v2.
 *  You can use this software according to the terms and conditions of the Mulan PSL v2.
 *  You may obtain a copy of Mulan PSL v2 at:
 *           http://license.coscl.org.cn/MulanPSL2
 *  THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 *  See the Mulan PSL v2 for more details.
 * */
package com.ljqc.impl.cli;

import com.ljqc.abs.cli.CliParser;
import com.ljqc.entity.CliCheckFileInfo;
import com.ljqc.entity.DepInfo;
import com.ljqc.entity.PackageInfo;
import com.ljqc.entity.command.MavenCommand;
import com.ljqc.utils.CommandUtils;
import com.ljqc.utils.FileUtils;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @ClassName MavenCliParser
 * @Description
 * @Date 2025/3/12 15:01
 * @Version V1.0
 **/
public class MavenCliParser extends CliParser {
    private static final Logger LOGGER = Logger.getLogger(MavenCliParser.class.getName());

    private static final Pattern DEP_REGEX = Pattern.compile("\\[INFO] +([^:]+:[^:]+):[^:]+:(native:)?([^:]+):[^:]+:([^ ]+)");

    public MavenCliParser() {
        super();
        LOGGER.log(Level.FINE, "初始化MavenCliParser，添加pom.xml检查规则");
        super.needCheckFileNames.add(CliCheckFileInfo.builder().name("pom.xml").endWith(false).build());
    }

    @Override
    public List<PackageInfo> parser(List<File> needParserFilePaths) {
        LOGGER.log(Level.INFO, "开始解析Maven项目依赖，需要解析的文件数量：{0}", needParserFilePaths.size());
        List<PackageInfo> result = new ArrayList<>();
        for (File needParserFilePath : needParserFilePaths) {
            LOGGER.log(Level.FINE, "处理文件：{0}", needParserFilePath.getAbsolutePath());
            String rootPath = needParserFilePath.getParentFile().getAbsolutePath();
            String uuid = UUID.randomUUID().toString();
            String treeOutPath = new File("").getAbsolutePath() + File.separator + "out_" + uuid + ".txt";
            LOGGER.log(Level.FINE, "生成临时输出文件：{0}", treeOutPath);

            //执行maven命令行命令
            LOGGER.log(Level.INFO, "执行Maven命令获取依赖树");
            CommandUtils.run(MavenCommand.create()
                    .cd(rootPath)
                    .mvn()
                    .dependency("tree")
                    .tn(1)
                    .outPutFile(treeOutPath)
                    .build()
            );

            //读取依赖树文件
            LOGGER.log(Level.FINE, "读取依赖树文件");
            List<String> treeLines = readTreeFile(treeOutPath);
            if (treeLines == null) {
                LOGGER.log(Level.WARNING, "依赖树文件读取失败");
                continue;
            }

            String list = CommandUtils.run(MavenCommand.create()
                    .cd(rootPath)
                    .mvn()
                    .dependency("list")
                    .tn(1)
                    .outputAbsoluteArtifactFilename(true)
                    .build(), true
            );
            Map<String, String> jarLocalPath = getJarLocalPath(list);
            LOGGER.log(Level.FINE, "成功读取依赖树，共 {0} 行", treeLines.size());

            //将原始格式转换成具有层级信息的格式
            LOGGER.log(Level.FINE, "开始转换依赖树格式");
            List<String> treeLevelLines = convertTreeLevel(treeLines, 3); /*这里的3代表依赖树文本前缀需要处理掉的无用字符的个数*/
            if (treeLevelLines == null) {
                LOGGER.log(Level.WARNING, "依赖树格式转换失败");
                continue;
            }
            LOGGER.log(Level.FINE, "依赖树格式转换完成，共 {0} 行", treeLevelLines.size());

            //根据依赖树信息提取组件信息
            LOGGER.log(Level.INFO, "开始提取组件信息");
            extractPackageInfo(result, needParserFilePath, treeLevelLines, jarLocalPath);
            LOGGER.log(Level.INFO, "组件信息提取完成，当前已解析 {0} 个组件", result.size());
        }
        LOGGER.log(Level.INFO, "Maven项目依赖解析完成，共解析 {0} 个组件", result.size());
        return result;
    }

    /**
     * 解析list结果，获取jar包本地路径
     * @date 2025/3/19 19:20
     * @param list
     * @return java.util.Map<java.lang.String,java.lang.String>
     */
    private Map<String, String> getJarLocalPath(String list) {
       Map<String, String> jarLocalPath = new HashMap<>();
        for (String depInfo : list.split("\n")) {
            Matcher matcher = DEP_REGEX.matcher(depInfo);
            if (matcher.find()) {
                String name = matcher.group(1);
                String group = matcher.group(2);
                if(group == null){
                    String version = matcher.group(3);
                    String path = matcher.group(4);
                    jarLocalPath.put(name + ":" + version, path);
                } else {
                    String path = matcher.group(3);
                    jarLocalPath.put(name + ":" + group, path);
                }
            }
        }
        return jarLocalPath;
    }

    /**
     * 读取依赖树结果文件
     *
     * @param outPath
     * @return java.util.List<java.lang.String>
     * @date 2025/3/13 16:28
     */
    private static List<String> readTreeFile(String outPath) {
        LOGGER.log(Level.FINE, "开始读取依赖树文件：{0}", outPath);
        List<String> treeLines = null;
        Path path = Paths.get(outPath);
        try {
            treeLines = Files.readAllLines(path, StandardCharsets.UTF_8);
            LOGGER.log(Level.FINE, "依赖树文件读取成功");
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "读取依赖树文件失败", e);
        } finally {
            try {
                Files.delete(path);
                LOGGER.log(Level.FINE, "临时文件删除成功：{0}", outPath);
            } catch (IOException e) {
                LOGGER.log(Level.WARNING, "临时文件删除失败", e);
            }
        }
        return treeLines;
    }

    /**
     * 根据依赖树信息提取组件信息
     *
     * @param result
     * @param needParserFilePath
     * @param treeLevelLines
     * @date 2025/3/13 16:26
     */
    private void extractPackageInfo(List<PackageInfo> result, File needParserFilePath, List<String> treeLevelLines, Map<String, String> jarLocalPath) {
        LOGGER.log(Level.FINE, "开始从依赖树中提取组件信息，文件：{0}", needParserFilePath.getAbsolutePath());
        for (int i = 1; i < treeLevelLines.size(); i++) {
            DepInfo convert = convert(treeLevelLines.get(i));
            String[] depData = convert.getDepData();
            int level = convert.getLevel();

            LOGGER.log(Level.FINE, "处理第 {0} 行依赖，级别：{1}", new Object[]{i, level});
            String version = depData.length > 5 ? depData[4] : depData[3];
            String realPath = jarLocalPath.get(depData[0] + ":" + depData[1] + ":" + version);
            PackageInfo packageInfo = PackageInfo.builder()
                    .name(depData[0] + ":" + depData[1])
                    .version(version) /*根据依赖树格式解析下标3 是组件版本*/
                    .fileName(needParserFilePath.getAbsolutePath())
                    .checksum(realPath == null ? null : FileUtils.calculateMD5(new File(realPath)))
                    .downloadUrl(realPath == null ? null : "https://repo.maven.apache.org/maven2/" + depData[0] + "/" + depData[1] + "/" + version + "/" + depData[1] + "-" + version + ".jar")
                    .type("maven")
                    .build();
            LOGGER.log(Level.FINE, "创建组件信息：{0}:{1}:{2}",
                    new Object[]{packageInfo.getName(), packageInfo.getVersion(), packageInfo.getType()});

            if (level != 0) {
                LOGGER.log(Level.FINE, "查找父级依赖信息");
                int parentIndex = level - 1;
                for (int j = i - 1; j >= 0; j--) {
                    String parentDep = treeLevelLines.get(j);
                    if (parentDep.startsWith(parentIndex + DELIMITER)) {
                        String[] parentDepData = parentDep.split(DELIMITER)[1].split(":");
                        packageInfo.setParentName(parentDepData[0] + ":" + parentDepData[1]);
                        packageInfo.setParentVersion(parentDepData[3]); /*根据依赖树格式解析下标3 是组件版本*/
                        LOGGER.log(Level.FINE, "设置父级依赖：{0}:{1}",
                                new Object[]{packageInfo.getParentName(), packageInfo.getParentVersion()});
                        break;
                    }
                }
            }
            result.add(packageInfo);
        }
        LOGGER.log(Level.FINE, "组件信息提取完成，本次提取 {0} 个组件", treeLevelLines.size() - 1);
    }

    /*匹配依赖树前缀无用字符正则*/
    private static final Pattern LEVEL_REGEX = Pattern.compile("^[-|+\\\\ ]*(?=([a-zA-Z]))");

    /**
     * 去除依赖树前缀字符，并补充层级信息
     *
     * @param treeLines
     * @param levelCount
     * @return java.util.List<java.lang.String>
     * @date 2025/3/13 16:03
     */
    private static List<String> convertTreeLevel(List<String> treeLines, int levelCount) {
        LOGGER.log(Level.FINE, "开始转换依赖树格式，行数：{0}，层级字符数：{1}",
                new Object[]{treeLines.size(), levelCount});
        LinkedList<String> result = null;
        try {
            result = treeLines.stream().map(source -> {
                StringBuilder sb = new StringBuilder(source);
                Matcher matcher = LEVEL_REGEX.matcher(source);
                int count = 0;
                if (matcher.find()) {
                    count = matcher.end();
                }
                return sb.replace(0, count, (count == 0 ? 1 : (count / levelCount)) + DELIMITER).toString();
            }).collect(Collectors.toCollection(LinkedList::new));
            LOGGER.log(Level.FINE, "依赖树格式转换成功");
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "依赖树格式转换失败", e);
        }
        return result;
    }
}
